﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using MoroMoro.Samples.ToDoList.Base.Application;
using MoroMoro.Samples.ToDoList.Base.Domain.Builders;
using MoroMoro.Samples.ToDoList.Application;
using MoroMoro.Samples.ToDoList.Presentation.Views;

namespace MoroMoro.Samples.ToDoList.Presentation.Presenters
{
    /// <summary>
    /// エンティティ構築プレゼンターです。
    /// </summary>
    /// <typeparam name="TView">ビューの型。</typeparam>
    /// <typeparam name="TEntity">エンティティの型。</typeparam>
    /// <typeparam name="TBuilder">ビルダの型。</typeparam>
    /// <typeparam name="TErrors">エラーの型。</typeparam>
    public abstract class BuildPresenter<TView, TEntity, TBuilder, TErrors> : Presenter<TView>
        where TView : IBuildView
        where TEntity : class
        where TBuilder : IEntityBuilder<TEntity>
        where TErrors : EntityBuildErrors
    {
        /// <summary>
        /// <see cref="BuildPresenter&lt;TView, TEntity, TBuilder, TErrors&gt;"/> の新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="view">ビュー。</param>
        /// <param name="facade">ファサード。</param>
        /// <param name="builder">ビルダ。</param>
        /// <exception cref="System.ArgumentNullException">view または facade または builder が null です。</exception>
        public BuildPresenter(TView view, ToDoListFacade facade, TBuilder builder)
            : base(view)
        {
            if (facade == null)
            {
                throw new ArgumentNullException("facade");
            }
            if (builder == null)
            {
                throw new ArgumentNullException("builder");
            }
            Builder = builder;
            Facade = facade;

            HasChange = false;
        }


        /// <summary>
        /// ファサードを取得します。
        /// </summary>
        protected readonly ToDoListFacade Facade;

        /// <summary>
        /// ビルダを取得します。
        /// </summary>
        protected readonly TBuilder Builder;


        /// <summary>
        /// 入力フィールドに変更があるかどうかを示す値を取得します。
        /// </summary>
        /// <returns>入力フィールドに変更がある場合は true、それ以外は false。</returns>
        protected bool HasChange
        {
            get;
            set;
        }


        /// <inheritdoc/>
        protected override void RegisterEventHandlers()
        {
            base.RegisterEventHandlers();

            View.OkButtonClick += OnOkButtonClick;
        }

        /// <inheritdoc/>
        protected override void OnClosing(object sender, CancelEventArgs e)
        {
            base.OnClosing(sender, e);

            if (!HasChange)
            {
                return;
            }
            if (View.ShowYesNoMessageBox("変更を破棄しますか？") == DialogResult.Yes)
            {
                return;
            }

            e.Cancel = true;
        }

        /// <summary>
        /// OK ボタンがクリックされた時の処理を行います。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void OnOkButtonClick(object sender, EventArgs e)
        {
            var oldHasChange = HasChange;
            HasChange = false;
            ExecuteBuild((errors) =>
            {
                HasChange = oldHasChange;
                View.CancelClose();
                UpdateInputErrors();
                var errorMessage = CreateErrorMessage(errors);
                View.ShowErrorMessageBox(errorMessage);

                var commitmentErrorsHandler = ErrorsHandlerCreator<ICommitmentErrorsHandler>.Create(() => 
                {
                    View.DisableOkButton();
                });
                errors.HandledBy(commitmentErrorsHandler);
            });
        }

        /// <summary>
        /// ビルドを実行します。
        /// </summary>
        /// <param name="errorHandler">エラーハンドラ。</param>
        protected abstract void ExecuteBuild(Action<TErrors> errorHandler);

        /// <summary>
        /// エラーメッセージを生成します。
        /// </summary>
        /// <param name="error">エラー。</param>
        /// <returns>エラーメッセージ。</returns>
        protected abstract string CreateErrorMessage(TErrors error);

        /// <summary>
        /// 入力フィールドのエラー表示を更新します。
        /// </summary>
        protected abstract void UpdateInputErrors();
    }
}
